package com.atguigu.tingshu.album.service.impl;

import com.alibaba.cloud.commons.lang.StringUtils;
import com.atguigu.tingshu.album.service.TestService;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void testLock() {
        try {
            //先尝试获取分布式锁 1.创建锁对象  2.调用获取锁方法
            RLock lock = redissonClient.getLock("lock");

            //获取锁成功后，才执行业务
            lock.lock();  //阻塞到获取锁成功为止
            //lock.tryLock(5, TimeUnit.SECONDS);  //参数1：等待锁获取最大时间  参数2：时间单位  获取锁失败返回false
            //lock.tryLock(3, 5, TimeUnit.SECONDS);//参数1：等待锁获取最大时间  参数2：锁过期时间，参数3：时间单位  获取锁失败返回false

            try {
                //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
                String value = redisTemplate.opsForValue().get("num");
                if (StringUtils.isBlank(value)) {
                    return;
                }
                //2.对获取到值进行+1操作
                int num = Integer.parseInt(value);
                redisTemplate.opsForValue().set("num", String.valueOf(++num));
            } finally {
                //业务执行完毕释放锁
                lock.unlock();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 读取数据方法 允许并发读。 但不允许进行并发读写，写读
     *
     * @return
     */
    @Override
    public String read() {
        System.out.println("read当前节点被调用：");
        //1.创建读写锁对象
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("myLock");
        //2.获取读锁对象
        RLock lock = rwlock.readLock();
        //3.获取读锁
        lock.lock(5, TimeUnit.SECONDS);

        //优先从缓存中获取数据
        String data = redisTemplate.opsForValue().get("data");
        if (StringUtils.isNotBlank(data)) {
            //TODO 模拟查询数据库
            data = "dbData";
            //将查询结果放入缓存
            redisTemplate.opsForValue().set("daa", data);
        }

        //todo 故意不释放读锁  - 5s后自动释放
        lock.unlock();
        return data;
    }

    /**
     * 写数据方法  不允许并发读写，写读。 先哪个操作来的先执行谁，等待写锁或者读锁释放，写数据方法才能继续执行
     */
    @Override
    public String write() {
        System.out.println("write当前节点被调用：");
        //1.创建读写锁对象
        RReadWriteLock rwlock = redissonClient.getReadWriteLock("myLock");
        //2.获取写锁对象
        RLock lock = rwlock.writeLock();
        //3.获取写锁
        lock.lock(5, TimeUnit.SECONDS);

        //删除缓存
        redisTemplate.delete("data");

        //模拟修改数据库
        String data = "writeData";

        //todo 故意释放写锁  -- 5s后自动释放
        lock.unlock();
        return data;
    }

    /**
     * 采用SpringDataRedis实现分布式锁
     * 原理：执行业务方法前先尝试获取锁（setnx存入key val），如果获取锁成功再执行业务代码，业务执行完毕后将锁释放(del key)
     */
//    @Override
//    public void testLock() {
//
//        //0.先尝试获取锁 setnx key val
//        //问题：锁可能存在线程间相互释放
//        //Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", "lock", 10, TimeUnit.SECONDS);
//        //解决：锁值设置为uuid
//        String uuid = UUID.randomUUID().toString();
//        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 10, TimeUnit.SECONDS);
//
//        if(flag){
//            //获取锁成功，执行业务代码
//            //1.先从redis中通过key num获取值  key提前手动设置 num 初始值：0
//            String value = stringRedisTemplate.opsForValue().get("num");
//            //2.如果值为空则非法直接返回即可
//            if (StringUtils.isBlank(value)) {
//                return;
//            }
//            //3.对num值进行自增加一
//            int num = Integer.parseInt(value);
//            stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
//
//            //4.将锁释放 判断uuid
//            //问题：删除操作缺乏原子性。
//            //if(uuid.equals(stringRedisTemplate.opsForValue().get("lock"))){ //线程一：判断是满足是当前线程锁的值
//            //    //条件满足，此时锁正好到期，redis锁自动释放了线程2获取锁成功，线程1将线程2的锁删除
//            //    stringRedisTemplate.delete("lock");
//            //}
//            //解决：redis执行lua脚本保证原子，lua脚本执行会作为一个整体执行
//
//            //执行脚本参数 参数1：脚本对象封装lua脚本，参数二：lua脚本中需要key参数（KEYS[i]）  参数三：lua脚本中需要参数值 ARGV[i]
//            //4.1 先创建脚本对象 DefaultRedisScript泛型脚本语言返回值类型 Long 0：失败 1：成功
//            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
//            //4.2设置脚本文本
//            String script = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
//                    "then\n" +
//                    "    return redis.call(\"del\",KEYS[1])\n" +
//                    "else\n" +
//                    "    return 0\n" +
//                    "end";
//            redisScript.setScriptText(script);
//            //4.3 设置响应类型
//            redisScript.setResultType(Long.class);
//            stringRedisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);
//        }else{
//            try {
//                //睡眠
//                Thread.sleep(100);
//                //自旋重试
//                this.testLock();
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }
//    }
}